目錄
新增一個加總的函數 sumab(),關鍵字 def 引入一個函數定義,它必須後跟函數名稱和帶括號的形式參數列表。構成函數體的語句從下一行開始,並且必須縮進。
def sumab(a,b):
return a+b
print(sumab(10,12))
print(sumab)
sumx = sumab
print(sumx(20,22))
print(sumx)
---------------------------------------
# 輸出結果如下:
22
<function sumab at 0x000001F5AC16F040>
42
<function sumab at 0x000001F5AC16F040>
函數的執行會引入一個用於函數局部變數的新符號表。 更確切地說,函數中所有的變數賦值都將儲存在局部符號表中;而變數引用會首先在局部符號表中查找,然後是外層函數的局部符號表,再然後是全局符號表,最後是內建名稱的符號表。 因此,全局變數和外層函數的變數不能在函數內部直接賦值(除非是在 global 語句中定義的全局變數,或者是在 nonlocal 語句中定義的外層函數的變數)。在函數被呼叫時,實際參數(實參)會被引入被呼叫函數的本地符號表中;因此,實參是通過按值呼叫 (call by reference) 傳遞的(其中值始終是物件引用而不是物件的值)。當一個函數呼叫另外一個函數時,將會為該呼叫新建一個新的本地符號表。
函數定義會把函數名引入當前的符號表中。函數名稱的值具有直譯器將其識別為用戶定義函數的類型。這個值可以分配給另一個名稱,該名稱也可以作為一個函數使用,這用作一般的重命名機制。
return 語句會從函數內部回傳一個值。 不帶表達式參數的 return 會回傳 None。 函數執行完畢退出也會回傳 None。
給函數定義有可變數量的參數也是可行的,最有用的形式是對一個或多個參數指定一個預設值。這樣新建的函數,可以用比定義時允許的更少的參數呼叫,比如
def ask_ok(prompt, retries=4, reminder='Please try again!'):
while True:
ok = input(prompt)
if ok in ('y', 'ye', 'yes'):
return True
if ok in ('n', 'no', 'nop', 'nope'):
return False
retries = retries - 1
if retries < 0:
raise ValueError('invalid user response')
print(reminder)
# 只給出必需的參數:
ask_ok('Do you really want to quit?')
# 給出一個可選的參數:
ask_ok('OK to overwrite the file?', 2)
# 給出所有的參數:
ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
這個範例還介紹了 in 關鍵字。它可以測試一個列表是否包含某個值。
執行函數定義時,預設參數值從左到右計算,這意味著在定義函數時,表達式會被計算一次,並且每次呼叫都會使用相同的「預先計算」值。所以下面的範例為 5 。
i = 5
def f(arg=i):
print(arg)
i = 6
f()
--------------------------
# 輸出結果如下:
5
當預設參數值是可變物件(例如列表或字典)時,函數修改物件(例如,透過將項目附加到列表),則預設參數值實際上會被修改。比如,下面的函數會儲存在後續呼叫中傳遞給它的參數:
def f1(a, L=[]):
L.append(a)
print('L address is ',id(L))
return L
print(f1(1))
print(f1(2))
print(f1(3))
--------------------------
# 輸出結果如下:
[1]
[1, 2]
[1, 2, 3]
但當預設參數值是不可變物件(例如字串或None)時,在函數中修改物件並不會在後續呼叫共享這個預設值,可以這樣寫這個函數:
def f2(a, L=None):
print('L type is ',type(L))
if L is None:
print('L is None')
L = []
print('L type is ',type(L))
L.append(a)
return L
print(f2(1))
print(f2(2))
--------------------------
# 輸出結果如下:
L type is <class 'NoneType'>
L is None
L type is <class 'list'>
[1]
L type is <class 'NoneType'>
L is None
L type is <class 'list'>
[2]
而詳細原因建議參考 Fredrik Lundh 這篇文章 Default Parameter Values in Python,基本的概念是函數也是物件,而參數是物件內的屬性,而當這個屬性是可修改的時,資料就會被保留下來;但當屬性是不可修改的時候,對這個屬性的修改會生成一個新的記憶體參照,而不是這個屬性本身,所以下一次再呼叫這個函數時,就不會被記錄下來。
可以使用 kwarg=value 的關鍵字參數來呼叫函數。例如下面的函數:
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.")
print("-- Lovely plumage, the", type)
print("-- It's", state, "!")
接受一個必需的參數(required arguments) - voltage 和三個可選的參數(state, action,和 type)。這個函數可以通過下面的任何一種方式呼叫:
parrot(1000) # 1 位置參數 (positional argument)
parrot(voltage=1000) # 1 關鍵字參數 (keyword argument)
parrot(voltage=1000000, action='VOOOOOM') # 2 關鍵字參數
parrot(action='VOOOOOM', voltage=1000000) # 2 關鍵字參數
parrot('a million', 'bereft of life', 'jump') # 3 位置參數
parrot('a thousand', state='pushing up the daisies') # 1 位置參數, 1 關鍵字參數
但下面的函數呼叫都是無效的:
parrot() # 缺乏必需參數
parrot(voltage=5.0, 'dead') # non-keyword argument after a keyword argument
parrot(110, voltage=220) # 相同參數重復給值
parrot(actor='John Cleese') # 不認識的關鍵字參數
在函數呼叫中,關鍵字參數必須跟隨在位置參數的後面。傳遞的所有關鍵字參數必須與函數接受的其中一個參數匹配,它們的順序並不重要。這也包括非可選參數,(比如 parrot(voltage=1000) 也是有效的)。不能對同一個參數多次賦值。
*args 任意參數(arbitrary arguments):程式設計師不知道要傳遞給函數的參數數量,可以接收任意數量的位置參數,即非關鍵字參數、可變長度參數清單的參數。出現在 *args 參數之後的任何形式參數都是 ‘關鍵字參數’,也就是說它們只能作為關鍵字參數而不能是位置參數。
def concat(*args, sep='/'):
if (sep == ''):
return list(args)
else:
sep.join(args)
print(concat("earth", "mars", "venus"))
print(concat("earth", "mars", "venus", sep="|"))
------------------------------------------
['earth', 'mars', 'venus']
earth|mars|venus
可以用 lambda 關鍵字來新建一個小的匿名函數。這個函數回傳兩個參數的和: lambda a, b: a+b 。lambda 函數可以在需要函數物件的任何地方使用,它在語法上限於單個表達式。從語義上來說,只是正常函數定義的語法,可以引用所包含域的變數:
def make_incrementor(n):
return lambda x: x + n
f = make_incrementor(42)
print(f(0)) # x=0, n=42
print(f(1)) # x=1, n=42
------------------------------------------
# 輸出結果如下:
42
43
從 Python 直譯器退出並再次進入,之前的定義(函數和變數)都會丟失。因此,如果想編寫一個稍長些的程序,最好使用一般編輯器為直譯器準備輸入並將該文件作為輸入執行。這被稱作編寫腳本 (script) 。隨著程序變得越來越長,你或許會想把它拆分成幾個文件,以方便維護。你亦或想在不同的程序中使用一個便捷的函數,而不必把這個函數複製到每一個程序中去。為支持這些需求,Python 有一種方法可以把定義放在一個文件裡,並在腳本或直譯器的互動式實例中使用它們。這樣的文件被稱作模組 (module),模組中的定義可以匯入 (import) 到其它模組或者主模組。
模組是一個包含 Python 定義和語句的文件。文件名就是模組名後跟附檔名 .py 。在一個模組內部,模組名可以通過全局變數 __name__ 的值獲得。例如,使用你最喜愛的文本編輯器在當前目錄下新建一個名為 calc.py 的文件, 文件中含有以下內容:
calc.py
def add(a,b):
return a + b
def sub(a,b):
return a - b
現在進入 Python 直譯器,並用以下命令匯入該模組,在當前的符號表中,這並不會直接進入到定義在 add/sub 函數內的名稱,它只是進入到模組名 calc 中。你可以用模組名訪問這些函數
>>>
>>> import calc
>>> calc.add(10,20)
30
>>> calc.sub(30,20)
10
>>> calc.__name__
'calc'
import 語句有一個變體,它可以把名字從一個被呼叫模組內直接匯入到現模組的符號表裡;可以匯入模組內定義的所有名稱,這會調入所有非以下划線(_)開頭的函數名稱,但在多數情況下,Python 程序員都不會使用這個功能,因為它在直譯器中引入了一組未知的名稱,而它們很可能會覆蓋一些你已經定義過的東西;模組名稱之後帶有 as,則跟在 as 之後的名稱將直接綁定到所匯入的模組,這種方式也可以在用到 from 的時候使用,並會有類似的效果;內建函數 dir() 用於查找模組定義的名稱。 它回傳一個排序過的字符串列表,列出所有類型的名稱:變數,模組,函數,等等。
>>> from calc import add, sub
>>> add(50,20)
70
>>> from calc import *
>>> add(50,20)
70
>>> import calc as c
>>> c.add(50,20)
70
>>> from calc import add as addOperation
>>> addOperation(50,20)
70
>>> dir(calc)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'add', 'sub']
當要匯入 calc 模組的時候,直譯器首先尋找具有該名稱的內建模組。如果沒有找到,然後直譯器從 sys.path 變數給出的目錄列表裡尋找名為 calc.py 的文件。sys.path 初始有這些目錄地址: